{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Room Control System\n",
    "\n",
    "![](images/room_clip.png)\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Aim\n",
    "\n",
    "This application notebook uses Grove sensors and actuators to control a room.\n",
    "\n",
    "## References \n",
    "* [Grove temperature](https://www.seeedstudio.com/Grove-Temperature-Sensor.html) \n",
    "* [Grove Servo](https://wiki.seeedstudio.com/Grove-Servo.html)  \n",
    "* [Grove Relay](https://www.seeedstudio.com/Grove-Relay.html)  \n",
    "* [Grove I2C OLED](https://wiki.seeedstudio.com/Grove-OLED_Display_0.96inch/) \n",
    "* [Grove RGB LED Stick](https://www.seeedstudio.com/Grove-RGB-LED-Stick-10-WS2813-Mini.html) \n",
    "* [Grove Slide Potentiometer](https://www.seeedstudio.com/Grove-Slide-Potentiometer.html) \n",
    "* [Grove Base Shield V2.0](https://www.seeedstudio.com/Base-Shield-V2.html) \n",
    "* [PYNQ Asyncio](https://pynq.readthedocs.io/en/latest/overlay_design_methodology/pynq_and_asyncio.html?highlight=asyncio)\n",
    "* [Color chart](https://www.color-blindness.com/color-name-hue/)\n",
    "\n",
    "## Last revised\n",
    "* 22 April 2021\n",
    "    + Initial version\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load _base_ Overlay"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "try {\n",
       "require(['notebook/js/codecell'], function(codecell) {\n",
       "  codecell.CodeCell.options_default.highlight_modes[\n",
       "      'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n",
       "  Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n",
       "      Jupyter.notebook.get_cells().map(function(cell){\n",
       "          if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n",
       "  });\n",
       "});\n",
       "} catch (e) {};\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/javascript": [
       "\n",
       "try {\n",
       "require(['notebook/js/codecell'], function(codecell) {\n",
       "  codecell.CodeCell.options_default.highlight_modes[\n",
       "      'magic_text/x-csrc'] = {'reg':[/^%%pybind11/]};\n",
       "  Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n",
       "      Jupyter.notebook.get_cells().map(function(cell){\n",
       "          if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n",
       "  });\n",
       "});\n",
       "} catch (e) {};\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from time import sleep\n",
    "\n",
    "from IPython.display import display, HTML\n",
    "from pynq.overlays.base import BaseOverlay\n",
    "from pynq_peripherals import ArduinoSEEEDGroveAdapter\n",
    "\n",
    "base = BaseOverlay('base.bit')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Constructing application with Grove Base Shield V2.0 (Arduino)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-box alert-danger\">\n",
    "    <h4 class=\"alert-heading\">Stop before proceeding further! </h4>\n",
    "     Please make sure you had connected all the sensors before switching the board ON. If the sensors are connected while the board is ON it can cause the system to shutdown. For safe use, you can note down the connections shown below, switch the board OFF make the physical connections and the switch the board back ON.\n",
    "    \n",
    "   <h4 class=\"alert-heading\">Make Physical Connections </h4>\n",
    "    <ul>\n",
    "        <li>Insert the Grove Base Shield into the Arduino connector on the board.</li>\n",
    "        <li>Connect the grove_potentiometer module to A3 connector of the Grove Base Shield.</li>\n",
    "        <li>Connect the grove_temperature module to A1 connector of the Grove Base Shield.</li>\n",
    "        <li>Connect the grove_servo module to D5 connector of the Grove Base Shield.</li>\n",
    "        <li>Connect the grove_relay module to D6 connector of the Grove Base Shield.</li>\n",
    "        <li>Connect the grove_led_stick module to D7 connector of the Grove Base Shield.</li>\n",
    "        <li>Connect the grove_oled module to any of the I2C connectors of the Grove Base Shield.</li></ul>\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](images/room_control.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Adapter configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "adapter = ArduinoSEEEDGroveAdapter(base.ARDUINO, \n",
    "                                   A3='grove_potentiometer', A1='grove_temperature', \n",
    "                                   D5='grove_servo', D6='grove_relay', D7='grove_led_stick',\n",
    "                                   I2C='grove_oled')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define device objects"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "blinds_slider = adapter.A3\n",
    "blinds_servo = adapter.D5\n",
    "temp_sensor = adapter.A1\n",
    "led_stick = adapter.D7\n",
    "light_relay = adapter.D6\n",
    "display = adapter.I2C"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-box alert-info\">\n",
    "   <h4 class=\"alert-heading\">Notes before running the next cell </h4>\n",
    "    <ul>\n",
    "        <li><b>Light control </b></li>\n",
    "        <ul><li>Toggle Switch SW0 on the board to turn the relay light ON or OFF.</li></ul>\n",
    "        <li><b>Blinds control </b></li>\n",
    "        <ul><li>Toggle Switch SW1 on the board to control the servo, switching ON will turn ON the Grove slider function, switching OFF will overwrite the Grove slider and turn off the servo</li></ul>\n",
    "        <li><b>Mood lights color control </b></li>\n",
    "        <ul><li> Press BTN0 button to scan through different colors of the led stick.</li></ul>\n",
    "        <ul><li>Press BTN1 button to turn OFF the led stick</li></ul>\n",
    "        <li><b>Exit</b></li>\n",
    "        <ul><li>Press BTN3 button to exit the application</li></ul>\n",
    "    </ul>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](images/rcs_ctl.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Basic loop implementation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "led_colors = {0xff: 'Blue', 0xe4ff: 'Aqua', 0xff5c: 'Spring', 0xff10: 'Lime',\n",
    "              0x3eff00: 'Harlequin', 0xfffa00: 'Yellow', 0xffc800: 'Tangerine',\n",
    "              0xff4600: 'Orange', 0xff2800: 'Scarlet', 0xff0000: 'Red'}\n",
    "\n",
    "color_keys = list(led_colors.keys())\n",
    "\n",
    "\n",
    "def set_leds(color):\n",
    "    for i in range(10):\n",
    "        led_stick.set_pixel(i, color)\n",
    "\n",
    "\n",
    "blinds_position = 0\n",
    "count = 0\n",
    "state = None\n",
    "set_leds(color_keys[0])\n",
    "led_stick.show()\n",
    "display.clear_display()\n",
    "display.set_next_row_wrap_mode()\n",
    "\n",
    "try:\n",
    "    while base.buttons[3].read() == 0:\n",
    "        if base.switches[1].read() == 1:\n",
    "            blinds_position = blinds_slider.position * 180\n",
    "            blinds_servo.set_angular_position(blinds_position)\n",
    "        else:\n",
    "            blinds_position = 0\n",
    "            blinds_servo.set_angular_position(blinds_position)\n",
    "\n",
    "        if base.switches[0].read() == 1:\n",
    "            light_relay.on()\n",
    "        else:\n",
    "            light_relay.off()\n",
    "\n",
    "        if base.buttons[0].read() == 1:\n",
    "            state = None\n",
    "            if count < 9:\n",
    "                count += 1\n",
    "            else:\n",
    "                count = 0\n",
    "            set_leds(color_keys[count])\n",
    "            led_stick.show()\n",
    "        elif base.buttons[1].read() == 1:\n",
    "            led_stick.clear()\n",
    "            state = 'clear'\n",
    "\n",
    "        display.set_position(0, 0)\n",
    "        display.put_string(f'Temp: ')\n",
    "        display.set_position(0, 6)\n",
    "        display.put_string(f'{temp_sensor.temperature*9/5+32:.1f} F')\n",
    "        display.set_position(2, 0)\n",
    "        display.put_string('Blinds are down' if (\n",
    "                blinds_position <= 90) else 'Blinds are up  ')\n",
    "        display.set_position(4, 0)\n",
    "        display.put_string('Lights are ON ' if (\n",
    "                base.switches[0].read() == 1) else 'Lights are OFF')\n",
    "        display.set_position(6, 0)\n",
    "        display.put_string(f'Mood light color: None         ' \n",
    "                           if (base.buttons[1].read() == 1 or state == 'clear')\n",
    "                           else f'Mood light color: {led_colors[color_keys[count]]}     ')\n",
    "\n",
    "    display.clear_display()\n",
    "    led_stick.clear()\n",
    "    blinds_servo.set_angular_position(0)\n",
    "    light_relay.off()\n",
    "except KeyboardInterrupt:\n",
    "    print('Kernel Interrupted')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Asyncio Coroutine Implementation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Basic loop implementation works well as the temperature sensor is queried in every iteration while the servo, relay and led_stick modules respond to actuation. All this happens in less than a second.   \n",
    "However, if the application requires the temperature sensor to be queried every 10 seconds or so, the servo, relay and led_stick modules will have to wait for the temperature sensor to complete and therefore cannot respond.   \n",
    "e.g.\n",
    "```Python\n",
    "try:\n",
    "    while base.buttons[3].read() == 0:\n",
    "        \n",
    "        ...\n",
    "        \n",
    "        display.put_string(f'{temp_sensor.temperature*9/5+32:.1f} F')\n",
    "        \n",
    "        sleep(10)\n",
    "        \n",
    "        ...\n",
    "        \n",
    "        # Code for servo, relay and led_stick control\n",
    "        \n",
    "        ...\n",
    "\n",
    "        # Display status for servo, relay and led_stick\n",
    "        # Clear Grove modules\n",
    "        \n",
    "        ...\n",
    "        \n",
    "except KeyboardInterrupt:\n",
    "    print('Stopping the loop')\n",
    "```\n",
    "\n",
    "To solve this problem we use the Asyncio coroutine implementation   \n",
    "   \n",
    "> The Python asyncio library manages multiple IO-bound tasks asynchronously, thereby avoiding any blocking caused by waiting for responses from slower IO subsystems. Instead, the program can continue to execute other tasks that are ready to run. When the previously-busy tasks are ready to resume, they will be executed in turn, and the cycle is repeated.   \n",
    "\n",
    "Asyncio coroutine implementation is shown below"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "import asyncio\n",
    "\n",
    "blinds_position = 0\n",
    "state = None\n",
    "temp = '32 F'\n",
    "count = 0\n",
    "\n",
    "led_colors = {0xff: 'Blue', 0xe4ff: 'Aqua', 0xff5c: 'Spring', 0xff10: 'Lime',\n",
    "              0x3eff00: 'Harlequin', 0xfffa00: 'Yellow', 0xffc800: 'Tangerine',\n",
    "              0xff4600: 'Orange', 0xff2800: 'Scarlet', 0xff0000: 'Red'}\n",
    "\n",
    "color_keys = list(led_colors.keys())\n",
    "\n",
    "\n",
    "def set_leds(color):\n",
    "    for i in range(10):\n",
    "        led_stick.set_pixel(i, color)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Temperature coroutine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "@asyncio.coroutine\n",
    "def temp_monitor_display(period):\n",
    "    global led_colors\n",
    "    global temp\n",
    "    \n",
    "    while True:\n",
    "        temp = f'{temp_sensor.temperature*9/5+32:.1f} F'\n",
    "        yield from asyncio.sleep(period)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Blinds servo control coroutine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "@asyncio.coroutine\n",
    "def blinds_controller(period):\n",
    "    global blinds_position\n",
    "    \n",
    "    while True:\n",
    "        # Manual overwrite with Grove slider if switch 1 is on\n",
    "        if base.switches[1].read() == 1:\n",
    "            blinds_position = blinds_slider.position * 180\n",
    "            blinds_servo.set_angular_position(blinds_position)\n",
    "        else:\n",
    "            blinds_position = 0\n",
    "            blinds_servo.set_angular_position(blinds_position)\n",
    "        yield from asyncio.sleep(period)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mood lights - Led stick coroutine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "@asyncio.coroutine\n",
    "def mood_leds(period):\n",
    "    global count\n",
    "    global color_keys\n",
    "    global state\n",
    "    \n",
    "    set_leds(color_keys[0])\n",
    "    led_stick.show()\n",
    "    \n",
    "    while True:\n",
    "        if base.buttons[0].read() == 1:\n",
    "            state = None\n",
    "            if count < 9:\n",
    "                count += 1\n",
    "            else:\n",
    "                count = 0\n",
    "            set_leds(color_keys[count])\n",
    "            led_stick.show()\n",
    "        elif base.buttons[1].read() == 1:\n",
    "            led_stick.clear()\n",
    "            state = 'clear'\n",
    "        yield from asyncio.sleep(period)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Light relay control and OLED display coroutine"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "@asyncio.coroutine\n",
    "def lights(period):\n",
    "    display.clear_display()\n",
    "    display.set_next_row_wrap_mode()\n",
    "    \n",
    "    while True:\n",
    "        if base.switches[0].read() == 1:\n",
    "            light_relay.on()\n",
    "        else:\n",
    "            light_relay.off()\n",
    "            \n",
    "        display.set_position(0, 0)\n",
    "        display.put_string(f'Temp: ')\n",
    "        display.set_position(0, 6)\n",
    "        display.put_string(temp)\n",
    "        display.set_position(2, 0)\n",
    "        display.put_string('Blinds are down' if (\n",
    "                    blinds_position <= 90) else 'Blinds are up  ')\n",
    "        display.set_position(4, 0)\n",
    "        display.put_string('Lights are ON ' if (\n",
    "                    base.switches[0].read() == 1) else 'Lights are OFF')\n",
    "        display.set_position(6, 0)\n",
    "        display.put_string(f'Mood light color: None         ' if (\n",
    "                base.buttons[1].read() == 1 or state == 'clear') else \n",
    "                f'Mood light color:{led_colors[color_keys[count]]}     ')\n",
    "        yield from asyncio.sleep(period)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create async tasks\n",
    "Temperature sensor is queried every 10 seconds while other modules are controlled multiple times every second"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "tasks = [asyncio.ensure_future(temp_monitor_display(10)),\n",
    "         asyncio.ensure_future(lights(0.3)),\n",
    "         asyncio.ensure_future(mood_leds(0.2)),\n",
    "         asyncio.ensure_future(blinds_controller(0.01))]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-box alert-info\">\n",
    "   <h4 class=\"alert-heading\">Notes before running the next cell </h4>\n",
    "    <ul>\n",
    "        <li><b>Light control </b></li>\n",
    "        <ul><li>Toggle Switch SW0 on the board to turn the relay light ON or OFF.</li></ul>\n",
    "        <li><b>Blinds control </b></li>\n",
    "        <ul><li>Toggle Switch SW1 on the board to control the servo, switching ON will turn ON the Grove slider function, switching OFF will overwrite the Grove slider and turn off the servo</li></ul>\n",
    "        <li><b>Mood lights color control </b></li>\n",
    "        <ul><li> Press BTN0 button to scan through different colors of the led stick.</li></ul>\n",
    "        <ul><li>Press BTN1 button to turn OFF the led stick</li></ul>\n",
    "        <li><b>Exit</b></li>\n",
    "        <ul><li>Press BTN3 button to exit the application</li></ul>\n",
    "    </ul>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](images/rcs_ctl.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Execute tasks and clear modules when tasks are cancelled"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "base.buttons[3].wait_for_value(1)\n",
    "[t.cancel() for  t in tasks]\n",
    "display.clear_display()\n",
    "led_stick.clear()\n",
    "blinds_servo.set_angular_position(0)\n",
    "light_relay.off()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright (C) 2021 Xilinx, Inc\n",
    "\n",
    "SPDX-License-Identifier: BSD-3-Clause"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
